<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>浓度分布图</title>
    <script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.5.1.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5.3.1/dist/echarts.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5.3.1/dist/extension/bmap.js"></script>
    <script type="text/javascript" src="https://trojx.oss-cn-hangzhou.aliyuncs.com/kriging-original.js"></script>
    <script type="text/javascript" src="https://trojx.oss-cn-hangzhou.aliyuncs.com/ol-debug.js"></script>
    <script type="text/javascript"
        src="https://api.map.baidu.com/api?v=3.0&ak=VqX1tpLOoCQRnVKr2cim2AK1ESUaNGgY"></script>

    <style>
        html,
        body,
        #container {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
            overflow: hidden;
            cursor: default;

        }

        #BaiduMap {
            width: 100%;
            height: 100%;
            overflow: hidden;
            margin: 0;
            font-family: "微软雅黑";
        }
    </style>
</head>

<body>
    <button onclick="SwitchView()">切换视图</button>
    <button onclick="ControlTimer()">启动/停止刷新</button>

    <!-- id="container"的div容器用于存放浓度分布图 -->
    <div id="container"></div>

    <!-- id="BaiduMap"的div容器用于存放百度地图 -->
    <div id="BaiduMap"></div>
</body>

</html>
<script>
    let myChart = echarts.init(document.getElementById("container"));

    var startlng = 121.1100; //起始经度
    var startlat = 30.2400;  //起始纬度
    var interval = 0.0003;   //坐标间隔
    var points = [];         //points存放经度坐标、纬度坐标、点位ID
    var values = [];         //values存放浓度值，本示例通过 Math.random()*10 生成0~10之间的随机数
    let data = [];           //data存放克里金插值计算后的数据，用于浓度分布图表的系列数据内容
    let maxValue;            //浓度最值，用于设置浓度图的visualMap
    let minValue;            //浓度最值，用于设置浓度图的visualMap
    var centerPoint;         //可用于初始化地图时设置中心点坐标
    let lngExtent = [];      //观测边缘经度
    let latExtent = [];      //观测边缘纬度
    let cellSizeCoord = [];  //单元格坐标

    // 图表颜色
    let COLORS = ["#006837", "#1a9850", "#66bd63", "#a6d96a", "#d9ef8b", "#ffffbf", "#fee08b", "#fdae61", "#f46d43", "#d73027", "#a50026"];

    //初始化实例后，首次设置数据
    autoChange();

    //获取数据并刷新浓度分布图
    function autoChange() {
        points = [];//清空数组
        values = [];//清空数组
        var id = 1; //点位ID
        // 如果采用克里金插值exponential计算，最多可输入100个values值
        for (var i = 0; i < 10; i++) {
            for (var j = 0; j < 10; j++) {
                points.push([startlng + j * interval, startlat - i * interval, id]);
                values.push([Math.random() * 10]);
                id++;
            }
        }
        //获得两个坐标连线的中间坐标,可用于初始化地图时设置中心点坐标
        var pointArr = [new BMap.Point(points[0][0], points[0][1]), new BMap.Point(points[points.length - 1][0], points[points.length - 1][1])];
        centerPoint = getcenterpoiont(pointArr);
        //获取浓度最值，用于设置浓度图的visualMap
        maxValue = getMaxMin(values, 'max');
        minValue = getMaxMin(values, 'min');


        //克里金插值数据封装
        let paramsK = {
            krigingModel: 'exponential',//model还可选'exponential','gaussian','spherical'
            krigingSigma2: 0,
            krigingAlpha: 100,
        };

        //逆时针,左 下 右 上  (观测点最边缘的经纬度)
        let extent = [points[0][0] - interval, points[points.length - 1][1] - interval, points[points.length - 1][0] + interval, points[0][1] + interval];
        let lngs = [];
        let lats = [];
        points.forEach(function (v) {
            lngs.push(v[0])
            lats.push(v[1])
        })
        //变差图;取样间隔有关的变分量                  //"exponential", 0, 100
        let variogram = kriging.train(values, lngs, lats, paramsK.krigingModel, paramsK.krigingSigma2, paramsK.krigingAlpha);
        //绘制多边形
        let polygons = [];
        polygons.push([[extent[0], extent[1]], [extent[0], extent[3]], [extent[2], extent[3]], [extent[2], extent[1]]]);
        //范围经度右-范围经纬左
        let grid = kriging.grid(polygons, variogram, (extent[2] - extent[0]) / 80);

        lngExtent = [extent[1], extent[3]];
        latExtent = [extent[0], extent[2]];
        //单元格个数
        let cellCount = [grid[0].length, grid.length];
        //单元格坐标
        cellSizeCoord = [(latExtent[1] - latExtent[0]) / cellCount[1], (lngExtent[1] - lngExtent[0]) / cellCount[0]];
        data = [];  //清空数组
        for (let i = 0; i < grid[0].length - 1; i++) { //宽
            for (let j = 0; j < grid.length - 1; j++) {  //高
                let d = [j, i, grid[j][i].toFixed(3)]  //toFixed 可把 Number 四舍五入为指定小数位数的数字
                data.push(d);
            }
        }


        //配置浓度分布图
        option = {
            tooltip: {  //提示框浮层
                // valueFormatter: (value) => "浓度： "+parseFloat(value).toFixed(3)  //数值显示部分的格式化回调函数，此处的value为字符串格式，若要调用toFixed需先parseFloat
            },
            visualMap: {  //visualMap 是视觉映射组件，用于进行『视觉编码』，也就是将数据映射到视觉元素（视觉通道）。
                // inverse: true, //是否反转 visualMap 组件
                precision: 3,  //数据展示的小数精度，默认为0，无小数点
                top: 10,       //visualMap 组件离容器上侧的距离。
                left: 10,      //visualMap 组件离容器左侧的距离。
                min: minValue, //指定 visualMapContinuous 组件的允许的最小值
                max: maxValue, //指定 visualMapContinuous 组件的允许的最大值
                inRange: {
                    color: COLORS,//color: 图元的颜色。
                    opacity: 0.7  //opacity: 图元以及其附属物（如文字标签）的透明度。
                },
                calculable: true,  //是否显示拖拽用的手柄（手柄能拖拽调整选中范围）。
                realtime: false,  //拖拽时，是否实时更新。如果为true则拖拽手柄过程中实时更新图表视图。如果为false则拖拽结束时，才更新视图。
            },
            bmap: {
                show: false,
                center: [centerPoint.lng, centerPoint.lat],  //中心点
                zoom: 20,    //缩放比例
                roam: true  //是否开启鼠标缩放和平移漫游。默认不开启。如果只想要开启缩放或者平移，可以设置成 'scale' 或者 'move'。设置成 true 为都开启。
            },
            series: [
                {
                    type: 'custom',  //custom:自定义系列
                    coordinateSystem: 'bmap', //该系列使用的坐标系
                    renderItem: renderItem,   //custom 系列需要开发者自己提供图形渲染的逻辑。这个渲染逻辑一般命名为 renderItem,对于 data 中的每个数据项（为方便描述，这里称为 dataItem)，会调用此 renderItem 函数。
                    animation: false,//是否开启动画。
                    emphasis: {      //强调
                        itemStyle: {
                            color: 'yellow'
                        }
                    },
                    encode: {       //encode定义 data 的哪个维度被编码成什么。
                        tooltip: 2  //表示维度 2 会在 tooltip 中显示。
                    },
                    data: data      //系列中的数据内容数组。
                }
            ],
        };

        // 通过 setOption 方法生成图表
        myChart.setOption(option);
    }

    /*  renderItem 函数提供了两个参数：
        params：包含了当前数据信息和坐标系的信息。
        api：是一些开发者可调用的方法集合。

        一般来说，renderItem 函数的主要逻辑，是将 dataItem 里的值映射到坐标系上的图形元素。这一般需要用到 renderItem.arguments.api 中的两个函数：
        api.value(...)，意思是取出 dataItem 中的数值。例如 api.value(0) 表示取出当前 dataItem 中第一个维度的数值。
        api.coord(...)，意思是进行坐标转换计算。例如 var point = api.coord([api.value(0), api.value(1)]) 表示 dataItem 中的数值转换成坐标系上的点。

        有时候还需要用到 api.size(...) 函数，表示得到坐标系上一段数值范围对应的长度。
        返回值中样式的设置可以使用 api.style(...) 函数，他能得到 series.itemStyle 中定义的样式信息，以及视觉映射的样式信息。也可以用这种方式覆盖这些样式信息。
    */
    function renderItem(params, api) {
        let lngIndex = api.value(0);
        let latIndex = api.value(1);
        let pointLeftTop = getCoord(params, api, lngIndex, latIndex);
        let pointRightBottom = getCoord(params, api, lngIndex + 1, latIndex + 1);
        return {
            type: 'rect',  //用 setOption 首次设定图形元素时必须指定,'rect':矩形。
            shape: {
                x: pointLeftTop[0],  //图形元素的左上角在父节点坐标系（以父节点左上角为原点）中的横坐标值。
                y: pointLeftTop[1],  //图形元素的左上角在父节点坐标系（以父节点左上角为原点）中的纵坐标值。
                width: pointRightBottom[0] - pointLeftTop[0],  //图形元素的宽度。
                height: pointRightBottom[1] - pointLeftTop[1]  //图形元素的高度。
            },
            style: api.style({
                stroke: 'rgba(0,0,0,0)' //笔画颜色。
            }),
            styleEmphasis: api.styleEmphasis() //风格强调
        };
    }

    function getCoord(params, api, lngIndex, latIndex) {
        var coords = params.context.coords || (params.context.coords = []);
        var key = lngIndex + '-' + latIndex;
        // bmap returns point in integer, which makes cell width unstable.
        //返回整数形式的点，这使得单元格宽度不稳定。
        // So we have to use right bottom point. 所以我们必须使用正确的底线
        return coords[key] || (coords[key] = api.coord([+(latExtent[0] + lngIndex * cellSizeCoord[0]).toFixed(6), +(lngExtent[0] + latIndex * cellSizeCoord[1]).toFixed(6)]));
    }

    // 可通过getBMap()方法获取百度地图对象，用这个对象以后可以使用百度地图API操作地图，获取到对象就可以添加标注等
    let map = myChart.getModel().getComponent('bmap').getBMap();
    addlabel(map); //给Echarts的地图对象添加点位标签，只能显示标签，点击事件无效


    // 百度地图API功能
    var BaiduMap = new BMap.Map("BaiduMap");
    BaiduMap.centerAndZoom(centerPoint, 20);   // 初始化地图，设置中心点坐标和地图级别
    BaiduMap.enableScrollWheelZoom(true);     //开启鼠标滚轮缩放
    addlabel(BaiduMap);//给百度地图API的地图对象添加点位标签

    function addlabel(map) {
        //循环添加标签及信息窗口
        points.forEach(function (v) {
            let targetPoint = new BMap.Point(v[0], v[1]); //定义坐标点，代表点位位置及label显示的位置
            //创建label
            let label = new BMap.Label(v[2], {
                offset: new BMap.Size(-20, -10), //label的偏移量，为了让label的中心显示在点上
                position: targetPoint  //label的位置
            });
            //给label设置样式
            label.setStyle({
                color: "red",
                maxWidth: "none",
                fontSize: "12px", //字体大小
                height: "20px",
                width: "30px",
                lineHeight: "20px",
                textAlign: "center", //文本居中
                fontFamily: "微软雅黑"
            });

            //设置信息窗口样式
            let opts = {
                width: 100,     // 信息窗口宽度
                height: 50,     // 信息窗口高度
                title: '点位ID: ' + v[2], // 信息窗口标题
                message: '设备信息'
            };
            //设置信息窗口内容
            let msg_info = "<a href='#' onclick=\"window.location.href='Plotly折线图.html?id=" + v[2] + "'\">查看历史曲线</a>";
            //创建信息窗口对象
            let infoWindow = new BMap.InfoWindow(msg_info, opts);

            //label添加点击事件
            label.addEventListener("click", function () {
                map.openInfoWindow(infoWindow, targetPoint); //开启信息窗口
            });

            //将label添加到地图
            map.addOverlay(label);
        })
    }

    //获得两个坐标连线的中间坐标,可用于初始化地图时设置中心点坐标
    function getcenterpoiont(pointArr) {
        var lng1 = parseFloat(pointArr[0].lng);
        var lat1 = parseFloat(pointArr[0].lat);

        var lng2 = parseFloat(pointArr[1].lng);
        var lat2 = parseFloat(pointArr[1].lat);

        var lngca = (Math.max(lng1, lng2) - Math.min(lng1, lng2)) / 2;
        var latca = (Math.max(lat1, lat2) - Math.min(lat1, lat2)) / 2;

        var lngcenter = Math.min(lng1, lng2) + lngca;
        var latcenter = Math.min(lat1, lat2) + latca;

        var pointcenter = new BMap.Point(lngcenter, latcenter);
        return pointcenter;
    }

    //获取数组中的最大值和最小值函数
    function getMaxMin(arr, param) {
        try {
            if (param == 'max') {
                if (typeof (Math.max.apply(null, arr)) != 'number') {
                    return "Error:element in arr is not a number!";
                } else {
                    return Math.max.apply(null, arr);
                }
            } else if (param == 'min') {
                if (typeof (Math.min.apply(null, arr)) != 'number') {
                    return "Error:element in arr is not a number!";
                } else {
                    return Math.min.apply(null, arr);
                }
            } else {
                return "Error:param is unsupported!";
            }
        } catch (e) {
            return "Error:" + e;
        }
    }

    //切换视图-设置浓度分布图是否显示
    function SwitchView() {
        var t = document.getElementById('container');
        if (t.style.display === 'none') {
            t.style.display = 'block';// 以块级元素显示
        } else {
            t.style.display = 'none'; // 隐藏
        }
    }

    //定时获取数据并刷新浓度分布图
    var Istimer = false;
    var timer;
    function ControlTimer() {
        if (Istimer == false) {
            Istimer = true;
            timer = setInterval(  //设置定时器，2s更新一次
                function () {
                    autoChange();  //获取数据并刷新浓度分布图
                }, 2000
            );
        }
        else if (Istimer == true) {
            Istimer = false;
            clearInterval(timer);//停止定时器
        }
    }
</script>